#include "XmlServerThread.hh"
#include "XmlSocket.hh"
#include "XmlMessage.hh"
#include "../World/User.hh"
#include "../World/Player.hh"
#include "../Exception.hh"
#include "ChatBroadcast.hh"
#include <QtCore>
#include <QtNetwork>
#include <QtXml>

using namespace Network;
using namespace World;

/**
 * \brief Statikus mutex
 *
 * A játékos - szál összerendelés konzisztenciájához kell.
 */
QMutex XmlServerThread::staticLock;

/// \brief Szerver leállítás folyamatban.
bool XmlServerThread::closeAll = false;

/// \brief Map, amiből kikereshető egy játékoshoz tartozó kiszolgáló szál.
XmlServerThread::ThreadMap XmlServerThread::threads;

/// \brief Map, amiből a csatlakozott de éppen nem játékban lév játék vannak
XmlServerThread::ThreadUserMap XmlServerThread::nonplayingusers;

/**
 * \brief Adott játékoshoz tartozó kiszolgáló szál lekérdezése.
 * \param player A játékos.
 * \return A playerhez tartozó szál vagy 0, ha nincs bejelentkezve.
 */
pXmlServerThread XmlServerThread::select(spPlayer player) {
    QMutexLocker sLocker(&staticLock);
    ThreadMap::ConstIterator thread = threads.find(player.data());

    if (thread == threads.end())
        return 0;

    return *thread;
}

void XmlServerThread::close_all() {
    QMutexLocker sLocker(&staticLock);
    closeAll = true;

    for (
      ThreadMap::iterator thread = threads.begin();
      thread != threads.end();
      ++thread
    ) {
        (*thread)->get_socket()->disconnectFromHost();
        (*thread)->wait();
    }

    threads.clear();
}

/// \brief Konstruktor.
XmlServerThread::XmlServerThread(
  int socketDescriptor,
  QSslConfiguration const& sslConfig,
  QObject* parent
):
    QThread(parent),
    socketDescriptor(socketDescriptor),
    socket(),
    sslConfig(sslConfig),
    state(START),
    user(),
    player() {}

XmlServerThread::~XmlServerThread() {
    if (!this->wait(10000))
        qWarning("thread join timeout");
}

/**
 * \brief Szálfüggvény.
 *
 * Regisztrálja a read_data() slotot a readyRead() signal-ra. Addig fut, amíg
 * a kapcsolat le nem záródik.
 */
void XmlServerThread::run() {
    socket = spXmlSocket(new XmlSocket(this));
    socket->setSslConfiguration(sslConfig);

    if (!socket->setSocketDescriptor(socketDescriptor)) {
        qDebug(
          "Hálózati hiba: %s\n", socket->errorString().toStdString().c_str()
        );
        return;
    }

    if (socket->state() != QAbstractSocket::ConnectedState)
        socket->waitForConnected(-1);

    socket->startServerEncryption();
    if (!socket->isEncrypted())
    	socket->waitForEncrypted();

    World::spUser user = User::construct(
    	socket->peerCertificate().subjectInfo(QSslCertificate::CommonName),
    	socket->peerCertificate().digest(QCryptographicHash::Sha1)
    );
    set_user(user);
    set_state(AUTH_OK);


    QHostAddress peerAddress = socket->peerAddress();
    u2 peerPort = socket->peerPort();
    qDebug(
      "client connected: %s:%d",
      peerAddress.toString().toStdString().c_str(),
      peerPort
    );

    connect(
      socket.data(),    SIGNAL(message_recieved()),
      this,             SLOT(process_message())
    );

    if (socket->state() != QAbstractSocket::UnconnectedState)
        socket->waitForDisconnected(-1);

    if (player) {
        QMutexLocker sLocker(&staticLock);
        threads.remove(player.data());
    }

    qDebug(
      "client disconnected: %s:%d",
      peerAddress.toString().toStdString().c_str(),
      peerPort
    );
}

/// \brief A socket lekérdezése.
spXmlSocket XmlServerThread::get_socket() {
    return socket;
}

/// \brief Állapot lekérdezése.
ConnectionState XmlServerThread::get_state() {
    return state;
}

/// \brief Állapot megváltoztatása.
void XmlServerThread::set_state(ConnectionState value) {
    state = value;
}

/// \brief A felhasználó lekérdezése, akit a szál kiszolgál.
World::spUser XmlServerThread::get_user() {
    return user;
}

/// \brief A kiszolgált felhasználó beállításe (bejelentkezéskor).
void XmlServerThread::set_user(World::spUser value) {
    user = value;

    if (user) {
        qDebug(
          "user logged in: %s@%s:%d",
          user->get_name().toStdString().c_str(),
          socket->peerAddress().toString().toStdString().c_str(),
          socket->peerPort()
        );
        //berakni a csatlakozott userek közé
        nonplayingusers[user.data()] = this;
    }
}

/// \brief A kiszolgált játékos lekérdezése.
spPlayer XmlServerThread::get_player() {
    return player;
}

/// \brief A kiszolgált játékos beállítása (játékhoz csatlakozáskor).
void XmlServerThread::set_player(spPlayer value) {
    QMutexLocker sLocker(&staticLock);

    if (player) {
    	nonplayingusers.remove(user.data());
        threads.remove(player.data());
    }

    player = value;

    if (player) {
        threads[player.data()] = this;
        qDebug(
          "player logged in: %s-%s@%s:%d",
          player->get_galaxy()->name.toStdString().c_str(),
          player->get_user()->get_name().toStdString().c_str(),
          socket->peerAddress().toString().toStdString().c_str(),
          socket->peerPort()
        );
    }
}

/**
 * \brief A socketen érkezett üzenet feldolgozása.
 *
 * Létrehoz egy konkrét XmlMessage példányt, amely majd elvégzi a feldolgozást.
 * Hiba esetén nem bontja a kapcsolatot, csak egy done üzenettel jelzi.
 */
void XmlServerThread::process_message() {
    {
        QMutexLocker sLocker(&staticLock);

        if (closeAll) {
            socket->disconnectFromHost();
            quit();
            return;
        }
    }

    try {
        spXmlMessage message = XmlMessage::construct(socket->read());
        message->process_on_server(*this);
    } catch (NonFatalProtocolError& ex) {
        qDebug("%s", ex.what());
        Done done(false, ex.what());
        done.send_to(*socket);
    }
}

/**
 * \brief Chat üzenetet küld az összes releváns játékosnak
 */
void XmlServerThread::broadcast_chat_message(QString message) {
	if (!player) {
		// nem vagyunk játékban, az üzenetet ki kell küldeni az összes nem
		// játékosnak

		ThreadUserMap::iterator i;
		for (i = nonplayingusers.begin(); i != nonplayingusers.end(); i++) {
			ChatBroadcast chatmsg(user->name, message);
			chatmsg.send_to(*(*i)->get_socket());
		}
	} else {
		// játékban vagyunk, csak a játékban lévő többi játékosnak kell
		// kiküldeni
		spGalaxy galaxy = player->get_galaxy();
		PlayerMapByUser others = galaxy->get_players();
		for (
		  PlayerMapByUser::const_iterator i = others.begin();
		  i != others.end();
		  ++i)
		{
			pXmlServerThread serverThread = XmlServerThread::select(i.value());

			if (serverThread) {
				ChatBroadcast chatmsg(user->name, message);
				chatmsg.send_to(*serverThread->get_socket());
			}
		}
	}
}
